// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// This is a small "shim" program which is used when wasm32 unit tests are run
// in this repository. This program is intended to be run in node.js and will
// load a wasm module into memory, instantiate it with a set of imports, and
// then run it.
//
// There's a bunch of helper functions defined here in `imports.env`, but note
// that most of them aren't actually needed to execute most programs. Many of
// these are just intended for completeness or debugging. Hopefully over time
// nothing here is needed for completeness.

const fs = require('fs');
const process = require('process');
const buffer = fs.readFileSync(process.argv[2]);

Error.stackTraceLimit = 20;

let m = new WebAssembly.Module(buffer);

let memory = null;

function copystr(a, b) {
  if (memory === null) {
    return null
  }
  let view = new Uint8Array(memory.buffer).slice(a, a + b);
  return String.fromCharCode.apply(null, view);
}

let imports = {};
imports.env = {
  // These are generated by LLVM itself for various intrinsic calls. Hopefully
  // one day this is not necessary and something will automatically do this.
  fmod: function(x, y) { return x % y; },
  exp2: function(x) { return Math.pow(2, x); },
  exp2f: function(x) { return Math.pow(2, x); },
  ldexp: function(x, y) { return x * Math.pow(2, y); },
  ldexpf: function(x, y) { return x * Math.pow(2, y); },
  log10: Math.log10,
  log10f: Math.log10,

  // These are called in src/libstd/sys/wasm/stdio.rs and are used when
  // debugging is enabled.
  rust_wasm_write_stdout: function(a, b) {
    let s = copystr(a, b);
    if (s !== null) {
      process.stdout.write(s);
    }
  },
  rust_wasm_write_stderr: function(a, b) {
    let s = copystr(a, b);
    if (s !== null) {
      process.stderr.write(s);
    }
  },

  // These are called in src/libstd/sys/wasm/args.rs and are used when
  // debugging is enabled.
  rust_wasm_args_count: function() {
    if (memory === null)
      return 0;
    return process.argv.length - 2;
  },
  rust_wasm_args_arg_size: function(i) {
    return Buffer.byteLength(process.argv[i + 2]);
  },
  rust_wasm_args_arg_fill: function(idx, ptr) {
    let arg = process.argv[idx + 2];
    let view = new Uint8Array(memory.buffer);
    Buffer.from(arg).copy(view, ptr);
  },

  // These are called in src/libstd/sys/wasm/os.rs and are used when
  // debugging is enabled.
  rust_wasm_getenv_len: function(a, b) {
    let key = copystr(a, b);
    if (key === null) {
      return -1;
    }
    if (!(key in process.env)) {
      return -1;
    }
    return Buffer.byteLength(process.env[key]);
  },
  rust_wasm_getenv_data: function(a, b, ptr) {
    let key = copystr(a, b);
    let value = process.env[key];
    let view = new Uint8Array(memory.buffer);
    Buffer.from(value).copy(view, ptr);
  },
};

let module_imports = WebAssembly.Module.imports(m);

for (var i = 0; i < module_imports.length; i++) {
  let imp = module_imports[i];
  if (imp.module != 'env') {
    continue
  }
  if (imp.name == 'memory' && imp.kind == 'memory') {
    memory = new WebAssembly.Memory({initial: 20});
    imports.env.memory = memory;
  }
}

let instance = new WebAssembly.Instance(m, imports);
